﻿using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System.Text;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Parsing;

namespace CsharpTool.SerilogCore
{
    /// <summary>
    /// CustomTextFormatter
    /// </summary>
    public class CustomTextFormatter : ITextFormatter
    {
        private readonly string _outputTemplate;
        private readonly string MessageKey = "Message";

        /// <summary>
        /// ctor
        /// </summary>
        public CustomTextFormatter(string outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
        {
            _outputTemplate = outputTemplate;
        }

        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        public void Format(LogEvent logEvent, TextWriter output)
        {
            if (string.IsNullOrWhiteSpace(_outputTemplate))
                return;
            var result = ToOutputMessage(_outputTemplate, logEvent);
            output.WriteLine(result);
        }

        #region private method
        private string ToOutputMessage(string outputTemplate, LogEvent logEvent)
        {
            var result = new StringBuilder();
            var outputDic = ParseOutputTemplate(outputTemplate);
            if (outputDic.Count == 0)
                return outputTemplate;
            foreach (var item in outputDic)
            {
                if (item.Key.Contains(nameof(logEvent.Timestamp)))
                {
                    var start = item.Key.IndexOf(':');
                    var end = item.Key.LastIndexOf('}');
                    result.Append(item.Value.Replace(item.Key, logEvent.Timestamp.ToString(item.Key.Substring(start + 1, end - start - 1))));
                }
                else if (item.Key.Contains(nameof(logEvent.Level)))
                {
                    result.Append(item.Value.Replace(item.Key, logEvent.Level.ToString()));
                }
                else if (item.Key.Contains(nameof(logEvent.TraceId)))
                {
                    result.Append(item.Value.Replace(item.Key, $"{logEvent.TraceId}"));
                }
                else if (item.Key.Contains(nameof(logEvent.SpanId)))
                {
                    result.Append(item.Value.Replace(item.Key, $"{logEvent.SpanId}"));
                }
                else if (item.Key.Contains(nameof(Environment.NewLine)))
                {
                    result.Append(item.Value.Replace(item.Key, Environment.NewLine));
                }
                else if (item.Key.Contains(nameof(logEvent.Exception)))
                {
                    if (logEvent.Exception == null)
                    {
                        result.Append(item.Value.Replace(item.Key, ""));
                    }
                    else
                    {
                        var error = $"{logEvent.Exception.Message}|StackTrace:{logEvent.Exception.StackTrace}";
                        if (logEvent.Exception.InnerException != null)
                            error = $"{error}{Environment.NewLine}->->InnerException:{logEvent.Exception.InnerException.Message}|{logEvent.Exception.InnerException.StackTrace}";
                        result.Append(item.Value.Replace(item.Key, error));
                    }
                }
                else if (item.Key.Contains(MessageKey))
                {
                    var message = ToMessage(logEvent);
                    result.Append(item.Value.Replace(item.Key, message));
                }
            }

            return result.ToString();
        }

        private Dictionary<string, string> ParseOutputTemplate(string outputTemplate)
        {
            var dic = new Dictionary<string, string>();
            if (string.IsNullOrWhiteSpace(outputTemplate))
                return dic;
            var matches = Regex.Matches(outputTemplate, "(.*?)(\\{.+?\\})(.*?)");
            var start = 0;
            var length = 0;
            foreach (Match match in matches)
            {
                if (match.Success)
                {
                    start = match.Groups[2].Value.IndexOf('{');
                    length = match.Groups[2].Value.IndexOf('}') - start + 1;
                    dic.Add(match.Groups[2].Value.Substring(start, length), match.Groups[0].Value);
                }
            }

            return dic;
        }

        private string ToMessage(LogEvent logEvent)
        {
            var message = new StringBuilder();
            var dic = ParseMessage(logEvent);
            foreach (var token in logEvent.MessageTemplate.Tokens)
            {
                if (token is PropertyToken propertyToken)
                {
                    if (dic.TryGetValue(propertyToken.PropertyName, out var value))
                    {
                        var t = value.GetType();
                        message.Append(JsonConvert.SerializeObject(value, Formatting.Indented));
                    }
                }
                else if (token is TextToken textToken)
                {
                    message.Append(textToken.Text);
                }
            }

            return message.ToString();
        }

        private Dictionary<object, object> ParseMessage(LogEvent logEvent)
        {
            var dic = new Dictionary<object, object>();
            foreach (var item in logEvent.Properties)
            {
                SetDic(dic, item.Key, item.Value);
            }

            return dic;
        }

        private void SetDic(Dictionary<object, object> dic, object key, LogEventPropertyValue value)
        {
            if (value is ScalarValue scalarValue)
            {
                dic.Add(key, scalarValue.Value!);
            }
            else if (value is StructureValue structureValue)
            {
                var v1 = new Dictionary<object, object>();
                dic.Add(key, v1);
                foreach (var p in structureValue.Properties)
                {
                    SetDic(v1, p.Name, p.Value);
                }
            }
            else if (value is DictionaryValue dictionaryValue)
            {
                var v1 = new Dictionary<object, object>();
                dic.Add(key, v1);
                foreach (var p in dictionaryValue.Elements)
                {
                    SetDic(v1, p.Key.Value!, p.Value);
                }
            }
            else if (value is SequenceValue arrayValue)
            {
                SetArray(dic, key, arrayValue);
            }
        }

        private void SetArray(Dictionary<object, object> dic, object key, SequenceValue arrayValue)
        {
            var v1 = new List<object>();
            dic.Add(key, v1);
            if (arrayValue.Elements.Count > 0)
            {

            }
            foreach (var p in arrayValue.Elements)
            {
                if (p is ScalarValue scalarValue1)
                    v1.Add(scalarValue1.Value!);
                else if (p is StructureValue structureValue)
                {
                    var v2 = new Dictionary<object, object>();
                    v1.Add(v2);
                    foreach (var p1 in structureValue.Properties)
                    {
                        SetDic(v2, p1.Name, p1.Value);
                    }
                }
                else if (p is DictionaryValue dictionaryValue)
                {
                    var v2 = new Dictionary<object, object>();
                    v1.Add(v2);
                    foreach (var p1 in dictionaryValue.Elements)
                    {
                        SetDic(v2, p1.Key.Value!, p1.Value);
                    }
                }
            }
        }

        #endregion
    }
}
